package com.zhongpengcheng.blog.util;

import cn.hutool.core.date.DateUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.signers.JWTSigner;
import com.zhongpengcheng.blog.dao.pojo.db.UserDO;
import com.zhongpengcheng.blog.enums.ResultCodeEnum;
import com.zhongpengcheng.blog.exception.UserException;
import com.zhongpengcheng.blog.pojo.dto.ReauthorizationDTO;
import com.zhongpengcheng.blog.properties.JWTProperties;
import com.zhongpengcheng.blog.service.UserService;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;

import static cn.hutool.jwt.RegisteredPayload.EXPIRES_AT;
import static cn.hutool.jwt.RegisteredPayload.SUBJECT;

/**
 * Token工具类，封装{@link JWT}相关的逻辑
 *
 * @author ZhongPengCheng
 * @version 1.0
 * @date 2021-08-14 23:13:00
 */
@Slf4j
public class TokenUtils {

    private TokenUtils() {
    }

    /**
     * JWT配置
     */
    @Setter
    private static JWTProperties properties;
    /**
     * 访问凭证签发人
     */
    @Setter
    private static JWTSigner accessSigner;
    /**
     * 刷新凭证签发人
     */
    @Setter
    private static JWTSigner refreshSigner;

    /**
     * 从http request中获取token
     * @param request http request
     * @return 本次访问的token
     */
    public static String getToken(HttpServletRequest request) {
        return request.getHeader(properties.getHeader());
    }

    /**
     * 从servlet request中获取token
     * @param request servlet request
     * @return 本次访问的token
     */
    public static String getToken(ServletRequest request) {
        return WebUtils.toHttp(request).getHeader(properties.getHeader());
    }

    /**
     * 根据userDO签发token
     * @return 签发的token
     */
    public static String createToken() {
        UserDO user = ShiroUtils.getCurrentSysUser();
        Assert.notNull(user, "用户对象为空");
        return createToken(user);
    }

    /**
     * 签发access token
     * @param user 用户信息
     * @return 访问凭证
     */
    private static String createToken(UserDO user) {
        Assert.notNull(user, "用户对象为空");
        return JWT.create()
                .setSubject(user.getUserId())
                .setExpiresAt(expireOfAccess())
                .setPayload("userId", user.getUserId())
                .setPayload("username", user.getUsername())
                .setPayload("nickName", user.getNickName())
                .setIssuedAt(new Date())
                .setIssuer(properties.getIssuer())
                .sign(accessSigner);
    }

    /**
     * 签发token刷新凭证
     * @return token刷新凭证
     */
    public static String createRefreshToken() {
        UserDO user = ShiroUtils.getCurrentSysUser();
        Assert.notNull(user, "用户对象为空");
        return signRefreshToken(user.getUserId(), expireOfRefresh());
    }

    /**
     * 获取refresh token
     */
    public static String getRefreshToken(HttpServletRequest request) {
        return request.getHeader(properties.getReAuthHeader());
    }

    /**
     * 重新签发access token，同时也会重新签发refresh token
     * @param refreshToken 签发许可凭证
     */
    public static ReauthorizationDTO refreshToken(String refreshToken) {
        // #1 解析token
        JWT jwt = JWT.of(refreshToken);
        // #2 来源验证
        if (!jwt.verify(refreshSigner)) {
            throw new UserException(ResultCodeEnum.INVALID_REFRESH_CREDENTIALS);
        }
        // #3 过期验证
        JSONObject payloads = jwt.getPayloads();
        Date expireAt = payloads.getDate(EXPIRES_AT);
        if (expireAt.before(new Date())) {
            throw new UserException(ResultCodeEnum.INVALID_REFRESH_CREDENTIALS);
        }
        // #4 查询用户信息
        UserDO user = SpringUtil.getBean(UserService.class).getUserByUserId(payloads.getStr(SUBJECT));
        // #5 使用user签发access token
        String accessToken = createToken(user);
        // #6 使用user签发refresh token
        String newRefreshToken = signRefreshToken(user.getUserId(), expireOfRefresh());

        return ReauthorizationDTO.builder()
                .refreshToken(newRefreshToken)
                .accessToken(accessToken)
                .build();
    }

    /**
     * 校验token是否过期
     * @param token 待校验token
     * @return 过期返回true，否则返回false
     */
    public static boolean isTokenExpire(@NonNull String token) {
        return token.length() <= 2 || token.length() % 4 != 0;
    }

    /**
     * 对外提供获取token http header配置的方法
     * @return token http header
     */
    public static String getHeader() {
        return properties.getHeader();
    }

    /**
     * 签发刷新凭证
     * @param userId 用户id
     * @param expire 过期时间
     * @return 刷新凭证
     */
    private static String signRefreshToken(String userId, Date expire) {
        return JWT.create()
                .setSubject(userId)
                .setExpiresAt(expire)
                .setIssuedAt(new Date())
                .setIssuer(properties.getIssuer())
                .sign(refreshSigner);
    }

    /**
     * 生成access token的过期时间
     */
    private static Date expireOfAccess() {
        return DateUtil.offsetSecond(new Date(), properties.getAccessExpire());
    }

    /**
     * 生成refresh token的过期时间
     */
    private static Date expireOfRefresh() {
        return DateUtil.offsetSecond(new Date(), properties.getRefreshExpire());
    }

    /**
     * 将refresh token添加至黑名单
     * @param token 刷新凭证
     * @return 添加是否成功
     */
    private static boolean addTokenToDenyList(String token) {
        // TODO: Redis实现token黑名单
        return true;
    }
}
